from odoo.http import request

from odoo.tools.safe_eval import safe_eval

from functools import partial

Operators = {
    'eq':  '=',   # 默认为 '='  if value is integer
    'neq': '!=',
    'gt':  '>',
    'ge':  '>=',
    'lt':  '<',
    'le':  '<=',
    'eq_null':   '=?',
    'eq_like':   '=like',
    'like':      'like',
    'not_like':  'not like',
    'ilike':     'ilike',
    'not_ilike': 'not ilike',
    'eq_ilike':  '=ilike',
    'in':        'in',         # 默认为 'in' if value is list
    'not_in':    'not in',
    'child_of':  'child_of',
    'parent_of': 'parent_of'
}


def _domain_dict2list(domain, domain2):
    # domain, domain2 两个参数, 分别传 list 和 dict 参数

    if not domain:
        domain = []

    if not domain2:
        domain2 = []

    def fn(field_op, value):
        field_op_list = field_op.split('___')
        if len(field_op_list) == 1:
            field_op_list = field_op_list + \
                [isinstance(value, list) and 'in' or 'eq']

        field, op = field_op_list
        op = Operators[op]
        return field, op, value

    if isinstance(domain, dict):
        domain = [fn(field_op, value)
                  for field_op, value in domain.items()]

    elif not isinstance(domain, list):
        # raise error
        domain = []

    if isinstance(domain2, dict):
        domain2 = [fn(field_op, value)
                   for field_op, value in domain2.items()]

    elif not isinstance(domain2, list):
        # raise error
        domain2 = []

    return domain + domain2


class OdooModel(object):
    def __init__(self, call_fn, model, context=None):
        if not context:
            context = {}
        context.update(request.env.context)
        odoo_model = request.env[model]
        odoo_model = odoo_model.with_context(context)
        print('context,env.context 2,', odoo_model.env.context)
        self.model = model
        self.obj = odoo_model
        self._call_fn = call_fn

    def __getattr__(self, attr):
        return partial(self._call_kw, attr)

    def _call_kw(self, method, *args, **kwargs):
        return self._call_fn(self.model, method, args, kwargs)


class Model2(object):
    def __call__(self, model, context=None):
        return self.__class__(self._call_fn, model, context=context)

    def __init__(self, call_fn, model, context=None):
        self.odoomodel = OdooModel(call_fn, model, context)
        self.model = model
        self._call_fn = call_fn
        self.obj = self.odoomodel.obj

    def onchange_spec(self, view_info, *args, **kwargs):
        print('xxxx,123456789, test_function', )
        return self.obj._onchange_spec(view_info)

    def test_function(self,  *args, **kwargs):
        print('xxxx,123456789, test_function', )
        return self.obj._onchange_spec()

    def fields_get2(self, allfields, attributes, *args, globals_dict=None, context=None, **kwargs):
        if not context:
            context = {}

        res = self.odoomodel.fields_get(
            allfields, attributes, context=context, *args, **kwargs)

        res_model_id = request.env.ref('base.model_res_partner').id

        session_info = request.env['ir.http'].session_info()
        company_id = session_info['company_id']
        user_companies = session_info['user_companies']
        current_company_id = user_companies['current_company'][0]

        allowed_company_ids = context.get('allowed_company_ids')
        if not allowed_company_ids:
            allowed_company_ids = list(map(lambda c: c[0],
                                           user_companies['allowed_companies']))

        if not globals_dict:
            globals_dict = {}

        globals_dict = globals_dict.copy()
        globals_dict.update({
            'company_id': company_id,
            'res_model_id': res_model_id,
            'current_company_id': current_company_id,
            'allowed_company_ids': allowed_company_ids
        })

        def call_safe_eval(domain, globals_dict):
            try:
                return safe_eval(domain, globals_dict)

            except Exception:
                return domain

        # for v in res.values():
        for k, v in res.items():
            if v['type'] in ['many2one']:
                if isinstance(v['domain'], str):
                    # print(' --- in odoorpc fields_get,', k, v)
                    domain = v['domain']
                    domain = call_safe_eval(domain, globals_dict)
                    # print(' --- in odoorpc fields_get,', domain)
                    v['domain'] = domain

        return res

    def onchange2(self, ids, values, field_name,
                  field_onchange=None,
                  field_onchange_ref=None,
                  field_onchange_model=None,
                  field_onchange_field=None,
                  context=None):
        # print('in odoorpc, onchange2', values, field_name)

        def call_onchange(ids, values, field_name,
                          field_onchange, context=None):

            res = self.odoomodel.onchange(ids, values, field_name,
                                          field_onchange, context=context)
            # print('----in odoorpc, onchange2, result----, ', res)
            res_value = res.get('value', {})
            res['value'] = self._return_for_read(res_value)
            if not res.get('domain'):
                res['domain'] = {}

            print('----in odoorpc, onchange2, result-2---, ', res)
            return res

        def get_field_onchange(field_onchange):
            if field_onchange:
                return field_onchange
            if not field_onchange_ref:
                return self.obj._onchange_spec()

            view_id = self.obj.env.ref(field_onchange_ref).id

            ref_obj = self.obj
            if field_onchange_model:
                ref_obj = self.obj.env[field_onchange_model]

            print('---1-----1---- mode, ',  field_onchange_model, ref_obj)
            view_info = ref_obj.fields_view_get(view_id)
            fld_onchg = ref_obj._onchange_spec(view_info)
            if not field_onchange_field:
                return fld_onchg

            return dict((k.split('.')[1], v) for k, v in fld_onchg.items()
                        if len(k.split('.')) == 2 and k.split('.')[0] == field_onchange_field)

        if not context:
            context = {}

        field_onchange = get_field_onchange(field_onchange)

        if isinstance(field_name, str) and not field_onchange.get(field_name):
            return {'domain': {}, 'value': {}, 'fields': {}}

        session_info = request.env['ir.http'].session_info()

        server_version_info = session_info['server_version_info']
        version = server_version_info[0]

        # is_call_default = not ids and not values and version == 13
        # line_ids 子表的 default_onchange,  values = {move_id: {...}}
        # default_get_onchange 时, field_name必须置空, 否则 需要额外的参数 来控制是
        is_call_default = not ids and not field_name and version == 13

        print('call 13, ', is_call_default)
        if is_call_default:
            fields = self.obj.fields_get([], ['type'])
            fields = list(fields.keys())

            values_default = self.odoomodel.default_get(
                fields, context=context)
            print('values_default', values_default)

            values2 = dict((k, None) for k in fields)
            values2.update(values)
            values2.update(values_default)
            res = call_onchange(ids, values2, field_name,
                                field_onchange, context=context)

            values_default = self._return_for_read(values_default)

            values_default.update(res.get('value', {}))
            res['value'] = values_default
            print('default onchange,', res)

        else:
            res = call_onchange(ids, values, field_name,
                                field_onchange, context=context)

        # fields = list(set([f for f in values] + [f for f in res['value']]))
        # fields in  传入的 values
        fields = [f for f in values]
        # print('-----1-----values,', fields)

        fieldsGet = self.obj.fields_get(fields, ['type', 'domain'])
        # print('-----1-----fieldsGet,', fieldsGet)

        # 传入的 fields 且是 m2o o2m m2m, 且是 domain=str的
        fld_in_fieldsGet = [
            f for f, v in fieldsGet.items()
            if v['type'] in ['many2one', 'one2many', 'many2many'
                             ] and isinstance(v['domain'], str)]

        # print('-----1-----fld_in_fieldsGet,', fld_in_fieldsGet)

        dict_fld_in_onchange = dict({}, **res['value'])
        dict_fld_in_onchange.update(res['domain'])

        # onchange 中的 并且 不在 values中的
        fld_in_onchange = [f for f in dict_fld_in_onchange if f not in fields]

        # onchange 中的 select 字段
        fld_in_onchange_select = [
            f for f in dict_fld_in_onchange
            if f in [f2 for f2, v2 in fieldsGet.items()
                     if v2['type'] in ['selection', 'many2one', 'one2many', 'many2many']]
        ]

        # 需要 补充 fields_get 的字段
        # 1 老 values 中, 需要更新 domain 的 relation 字段
        # 2 onchange 的 value 和 domain 中 新出现的字段
        # 3 onchange 的 relation 字段
        fields = list(
            set(fld_in_fieldsGet + fld_in_onchange + fld_in_onchange_select))
        # print('-----1-----fields,', fields)

        # 这个 4个模型, 需要 globals_dict
        # 其余模型, globals_dict, 只是 需要 company_id
        # 参数 values 及 onchange 的 value 中 包括这些数据
        # res.partner  state_id [('country_id', '=?', country_id)]
        # sale.order.line product_uom [('category_id', '=', product_uom_category_id)]
        # purchase.order.line product_uom [('category_id', '=', product_uom_category_id)]
        # stock.move product_uom [('category_id', '=', product_uom_category_id)]

        globals_dict = values.copy()
        globals_dict.update(self._return_formdata(res['value']))

        fields = self.fields_get2(
            fields, ['type', 'selection', 'relation', 'domain', 'readonly'],
            globals_dict=globals_dict, context=context)

        # 补充 m2m 字段的子字段
        # o2m 这里需要的 globals_dict, 只有 company_id
        # 目前 补充 o2m字段 只有一处, po 登记账单时, call account.move 的 default_get_onchange

        globals_dict2 = {}

        # 在 fields_get 里 自动补充 company_id, 这个不再需要了
        # if context.get('allowed_company_ids'):
        #     globals_dict2['company_id'] = context.get('allowed_company_ids')[0]
        # if globals_dict.get('company_id'):
        #     globals_dict2['company_id'] = globals_dict.get('company_id')

        for o2m_field, o2m_meta in fields.items():
            # print('o2m_field', o2m_field, o2m_meta['type'])
            if o2m_meta['type'] != 'one2many':
                continue

            o2m_value = res['value'].get(o2m_field)
            # print('o2m_field, o2m_value', o2m_field, o2m_value)
            if not o2m_value or not isinstance(o2m_value, (list, tuple)):
                continue

            line_fields_dict = {}
            for line in o2m_value:
                if line[0] in [1, 0]:
                    line_fields_dict.update(line[2])

            # print('xxxx,------------, o2m,fields,--',
            #       o2m_field, line_fields_dict)

            line_fields = list(line_fields_dict.keys())

            o2m_meta['fields'] = self(o2m_meta['relation'], context=context).fields_get2(
                line_fields, ['type', 'selection', 'relation', 'domain', 'readonly'], globals_dict=globals_dict2)

        res['fields'] = fields

        return res

    def _return_formdata(self, record):
        metadata = self.obj.fields_get(list(record.keys()), ['type'])

        def fn(k, v):
            meta = metadata.get(k, {})
            meta_type = meta.get('type')
            if meta_type in ['many2one', 'selection'] and isinstance(v, (list, tuple)):
                return v[0]
            return v

        return dict((k, fn(k, v)) for k, v in record.items())

    def name_search2(self, name='', domain=None, domain2=None, operator='ilike', limit=0, context=None, *args, **kwargs):
        domain = _domain_dict2list(domain, domain2)
        args = self.domain_and_company(domain)
        # name='', args=None, operator='ilike', limit=100
        return self.odoomodel.name_search(name=name, args=args, operator=operator, limit=limit, context=context)

    def domain_and_company(self, domain):
        if not domain:
            domain = []

        fgs = self.obj.fields_get(['company_id'], ['type'])
        if fgs:
            cids = self.obj.env.context.get('allowed_company_ids')
            if cids:
                domain.append(('company_id', '=', cids[0]))

        return domain

    def search_count2(self, domain=None, domain2=None, *args, **kwargs):
        domain = _domain_dict2list(domain, domain2)
        domain = self.domain_and_company(domain)
        return self.odoomodel.search_count(domain)

    def search_read2(self, domain=None,  domain2=None, fields=None,
                     offset=0, limit=None, order=None, context=None, *args, **kwargs):
        domain = _domain_dict2list(domain, domain2)
        domain = self.domain_and_company(domain)
        result = self.odoomodel.search_read(
            domain, fields=fields, offset=offset, limit=limit, order=order, context=context)
        return self._return_for_read_multi(result)

    def web_search_read2(self, fields=False, offset=0, limit=False, domain=None, domain2=None, sort=None):
        domain = _domain_dict2list(domain, domain2)
        domain = self.domain_and_company(domain)
        result = self._call_fn(self.model, fields, offset, limit, domain, sort)
        result2 = {
            'length': result['length'],
            'records': self._return_for_read_multi(result['records'])
        }
        return result2

    def read2(self, ids, fields=None, context=None):
        result = self.odoomodel.read(ids, fields=fields, context=context)
        return self._return_for_read_multi(result)

    def read_one2(self, ids, fields=None, context=None):
        result = self.read2(ids, fields=fields, context=context)
        if result:
            return result[0]
        return {}

    def write2(self, rid, values, fields=None, context=None):
        self.odoomodel.write(rid, values, context=context)
        return self.read_one2(rid, fields=fields, context=context)

    def create2(self, values, fields=None, context=None):
        rid = self.odoomodel.create(values, context=context)
        return self.read_one2(rid, fields=fields, context=context)

    # report wirard

    def report_print(self, values, *args, **kwargs):
        # check 是否自定义了 print_report2
        # if ok, call 自定义的 print_report2, 并把 report_print1 作为参数传进去
        # else call report_print1
        if hasattr(self.obj, 'report_print'):
            return self.obj.report_print(values, self.report_print1)
        else:
            return self.report_print1(values)

    def report_print1(self, values, *args, **kwargs):
        wizard = self.obj.create(values)
        meta = wizard.check_report()

        report_name = 'report.%s' % meta['report_name']
        data = meta['data']
        context = {}
        context.update(meta.get('context', {}))
        context.update({
            'active_model': wizard._name,
            'active_id': wizard.id,
            'active_ids': [wizard.id]
        })

        report_obj = request.env[report_name].with_context(context)

        fn = hasattr(report_obj, '_get_report_values2'
                     ) and report_obj._get_report_values2 or report_obj._get_report_values

        res = fn(None, data=data)
        return res

    def _return_for_read_multi(self, records, metadata=None):
        if not records:
            return records

        if not metadata:
            metadata = self.obj.fields_get(
                list(records[0].keys()), ['type', 'selection', 'relation', 'domain'])

        return [self._return_for_read(rec, metadata)
                for rec in records]

    # 处理 返回结果 的格式化
    # 2020-11-12 调整为:
    # m2o: id
    # m2o__name: name
    # selection: value
    # selection__name: name

    # 2020-11-12 以前的:
    # m2o: [0, name] or null
    # selection: [value, name]
    # o2m: [op, rid, {...嵌套处理}], 只针对 onchange 的返回值
    # o2m: [ ids ],  这是 read 的返回值

    def _return_for_read(self, record, metadata=None):
        if not metadata:
            metadata = self.obj.fields_get(
                list(record.keys()), ['type', 'selection', 'relation', 'domain'])

        return recursion_return_for_read_one(record, metadata)


def recursion_return_for_read_one(record, metadata):
    # print(record, metadata)
    def fn_o2m(line, o2m_meta):
        if line[0] not in [0, 1]:
            return line
        op, rid, vals = line
        return [op, rid, recursion_return_for_read_one(vals, o2m_meta)]

    def fn(k, v):
        meta = metadata.get(k, {})
        meta_type = meta.get('type')
        # print('xxxx, recursion_return, ', k, v, meta)
        if meta_type == 'selection':
            if not v:
                return None
            selection = dict(meta['selection'])
            return [v, selection[v]]
        elif meta_type == 'many2one':
            if not v:
                return None
            if isinstance(v, list):
                return v
            if not isinstance(v, int):
                return v

            return request.env[meta['relation']].browse(v).name_get()[0]

        elif meta_type == 'one2many':
            # print('-----xxxx, recursion_return, o2m, ', k,  meta)
            # onchange 会带 o2m 字段
            if not v:
                return v
            if not isinstance(v, (list, tuple)):
                return v
            if not isinstance(v[0], (list, tuple)):
                # 这是 普通的 查询 返回值 [ids]
                return v

            # 这是 onchange 的返回值, [(op,id, {vals})]
            o2m_meta = request.env[meta['relation']].fields_get(
                [], ['type', 'selection', 'relation', 'domain'])
            # print('xxxx, recursion_return, o2m_meta, ', k, v, o2m_meta)
            res = [fn_o2m(ln, o2m_meta) for ln in v]
            return res

        elif meta_type in ['char', 'text', 'html', 'date', 'datetime']:
            return v or None
        return v

    # res1 = dict((k, fn(k, v)) for k, v in record.items())

    # res1 = dict((k, fn(k, v)) for k, v in record.items())

    res = {}
    for k, v in record.items():
        meta = metadata.get(k, {})
        meta_type = meta.get('type')
        # print('xxxx, recursion_return, ', k, v, meta)

        v2 = fn(k, v)
        res2 = {}
        if meta_type in ['selection', 'many2one']:
            v2 = v2 or [None, None]
            res2 = {k: v2[0], k+'__name': v2[1]}
        else:
            res2 = {k: v2}

        res.update(res2)

    return res
